home *** CD-ROM | disk | FTP | other *** search
/ PC Users 1998 March / Pc Users extra 6.iso / pshare95 / prog / zeus / grep.c_ / grep.c
Encoding:
C/C++ Source or Header  |  1995-07-25  |  19.4 KB  |  695 lines

  1. /*
  2.  *
  3.  *
  4.  * The   information  in  this  document  is  subject  to  change
  5.  * without  notice  and  should not be construed as a commitment
  6.  * by Digital Equipment Corporation or by DECUS.
  7.  *
  8.  * Neither Digital Equipment Corporation, DECUS, nor the authors
  9.  * assume any responsibility for the use or reliability of  this
  10.  * document or the described software.
  11.  *
  12.  *   Copyright (C) 1980, DECUS
  13.  *
  14.  *
  15.  * General permission to copy or modify, but not for profit,  is
  16.  * hereby  granted,  provided that the above copyright notice is
  17.  * included and reference made to  the   fact  that  reproduction
  18.  * privileges were granted by DECUS.
  19.  *
  20.  */
  21. #include <dir.h>
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <ctype.h>
  26. /*
  27.  * grep.
  28.  *
  29.  * Runs on the Decus compiler or on vms.
  30.  * Converted for BDS compiler (under CP/M-80), 20-Jan-83, by Chris Kern.
  31.  *
  32.  * Converted to IBM PC with CI-C86 C Compiler June 1983 by David N. Smith
  33.  *
  34.  * On vms, define as:
  35.  *
  36.  *   grep :== "$disk:[account]grep"     (native)
  37.  *   grep :== "$disk:[account]grep grep"     (Decus)
  38.  *
  39.  * See below for more information.
  40.  *
  41.  */
  42.  
  43. /*****************************************************************/
  44. /* Changed by JAJ 5/96 to wrtie to STDOUT and not to STDERR so   */
  45. /* that the output is easier to capture to file. Also added a    */
  46. /* basic form of wildcard support was added                      */
  47. /*****************************************************************/
  48.  
  49. /*****************************************************************/
  50. /* Changed by BKB 5/97 to support wildcards along with a path    */
  51. /* Now prints the path along with the filename                   */
  52. /* Now is case sensitive by default, ignore case with -i option  */
  53. /* All non ANSI-C code is rewritten                              */
  54. /* Don't run if no files is given (don't use stdin)              */
  55. /*****************************************************************/
  56.  
  57. static void usage(char *s);
  58. static void help(char **hp);
  59. static void cant(char *s);
  60. static void compile(char *source);
  61. static void badpat(char *message, char *source, char *stop);
  62. static int match(void);
  63. static void grep(FILE *fp, char *fn);
  64. static char *split_file_name(char *file_name);
  65. static void error(char *s);
  66. static void file(char *s);
  67. static void store(char op);
  68. static char *cclass(char *source, char *src);
  69. static char *pmatch(char *line, char *pattern);
  70. static char grep_tolower(char c);
  71.  
  72. static char *documentation[] =
  73. {
  74.   "grep searches files for a given pattern.  Execute by",
  75.   "   grep [flags] regular_expression file_list",
  76.   "",
  77.   "Flags are single characters preceeded by '-':",
  78.   "   -c      Only a count of matching lines is printed",
  79.   "   -f      Print file name for matching lines switch, see below",
  80.   "   -n      Each line is preceeded by its line number",
  81.   "   -v      Only print non-matching lines",
  82.   "   -i      Ignore case",
  83.   "",
  84. /* -- JAJ  "The file_list is a list of files (wildcards are acceptable on RSX modes).", */
  85.   "The file_list is a list of files (wildcards are acceptable modes).",
  86.   "",
  87.   "The file name is normally printed if there is a file given.",
  88.   "The -f flag reverses this action (print name no file, not if more).",
  89.   "",
  90.   0
  91. };
  92.  
  93. char *patdoc[] =
  94. {
  95.   "The regular_expression defines the pattern to search for."
  96.   "Blank lines never match."
  97.   "The expression should be quoted to prevent file-name translation.",
  98.   "x      An ordinary character (not mentioned below) matches that character.",
  99.   "'\\'    The backslash quotes any character.  \"\\$\" matches a dollar-sign.",
  100.   "'^'    A circumflex at the beginning of an expression matches the",
  101.   "       beginning of a line.",
  102.   "'$'    A dollar-sign at the end of an expression matches the end of a line.",
  103.   "'.'    A period matches any character except \"new-line\".",
  104.   "':a'   A colon matches a class of characters described by the following",
  105.   "':d'     character.  \":a\" matches any alphabetic, \":d\" matches digits,",
  106.   "':n'     \":n\" matches alphanumerics, \": \" matches spaces, tabs, and",
  107.   "': '     other control characters, such as new-line.",
  108.   "'*'    An expression followed by an asterisk matches zero or more",
  109.   "       occurrances of that expression: \"fo*\" matches \"f\", \"fo\"",
  110.   "       \"foo\", etc.",
  111.   "'+'    An expression followed by a plus sign matches one or more",
  112.   "       occurrances of that expression: \"fo+\" matches \"fo\", etc.",
  113.   "'-'    An expression followed by a minus sign optionally matches",
  114.   "       the expression.",
  115.   "'[]'   A string enclosed in square brackets matches any character in",
  116.   "       that string, but no others.  If the first character in the",
  117.   "       string is a circumflex, the expression matches any character",
  118.   "       except \"new-line\" and the characters in the string.  For",
  119.   "       example, \"[xyz]\" matches \"xx\" and \"zyx\", while \"[^xyz]\"",
  120.   "       matches \"abc\" but not \"axb\".  A range of characters may be",
  121.   "       specified by two characters separated by \"-\".  Note that,",
  122.   "       [a-z] matches alphabetics, while [z-a] never matches.",
  123.   "The concatenation of regular expressions is a regular expression.",
  124.   0
  125. };
  126.  
  127. #define LMAX   512
  128. #define PMAX   256
  129. #define CHAR   1
  130. #define BOL  2
  131. #define EOL  3
  132. #define ANY  4
  133. #define CLASS  5
  134. #define NCLASS   6
  135. #define STAR   7
  136. #define PLUS   8
  137. #define MINUS  9
  138. #define ALPHA  10
  139. #define DIGIT  11
  140. #define NALPHA   12
  141. #define PUNCT  13
  142. #define RANGE  14
  143. #define ENDPAT   15
  144.  
  145. static int  cflag;
  146. static int  fflag;
  147. static int  nflag;
  148. static int  vflag;
  149. static int  nfile;
  150. static int  ignore_case_flag = 0; /* BKB */
  151. static int  debug = 0;     /* Set for debug code      */
  152. static char   *pp;
  153. static char   lbuf[LMAX];
  154. static char   pbuf[PMAX];
  155. /*******************************************************/
  156.  
  157. int main(int argc, char *argv[])
  158. {
  159.   register char   *p;
  160.   char *path;
  161.   register int    c, i;
  162.   int        gotpattern;
  163.   FILE       *f;
  164.  
  165.   /* -- JAJ wildcard support */
  166.   int err;
  167.   int count = 0;
  168.   struct ffblk findbuf;
  169.  
  170.   if(argc <= 1)
  171.     usage("No arguments");
  172.   if(argc == 2 && argv[1][0] == '?' && argv[1][1] == 0)
  173.   {
  174.     help(documentation);
  175.     help(patdoc);
  176.     return 1;
  177.   }
  178.   /* -- JAJ display the command line entered */
  179.   for (i = 0; i < argc; ++i)
  180.   {
  181.      printf("%s ", argv[i]);
  182.   }
  183.   printf("\n");
  184.  
  185.   nfile = argc - 1;
  186.   gotpattern = 0;
  187.   for(i = 1; i < argc; ++i)
  188.   {
  189.     p = argv[i];
  190.     if(*p == '-')
  191.     {
  192.       ++p;
  193.       while((c = *p++) != 0)
  194.       {
  195.         switch(grep_tolower(c))
  196.         {
  197.           case '?':
  198.             help(documentation);
  199.             break;
  200.           case 'C':
  201.           case 'c':
  202.             ++cflag;
  203.             break;
  204.           case 'D':
  205.           case 'd':
  206.             ++debug;
  207.             break;
  208.           case 'F':
  209.           case 'f':
  210.             ++fflag;
  211.             break;
  212.           case 'n':
  213.           case 'N':
  214.             ++nflag;
  215.             break;
  216.           case 'v':
  217.           case 'V':
  218.             ++vflag;
  219.             break;
  220.           case 'i':
  221.           case 'I':
  222.             ignore_case_flag++;
  223.             break;
  224.           default:
  225.             usage("Unknown flag");
  226.             break;
  227.         }
  228.       }
  229.       argv[i] = 0;
  230.       --nfile;
  231.     }
  232.     else if(!gotpattern)
  233.     {
  234.       compile(p);
  235.       argv[i] = 0;
  236.       ++gotpattern;
  237.       --nfile;
  238.     }
  239.   }
  240.   if(!gotpattern)
  241.     usage("No pattern");
  242.   if(nfile == 0)
  243.     usage("No files"); /* Let grep terminate if no filename is set (don't use stdin) BKB */
  244.   else
  245.   {
  246.     /* -- JAJ now supports file names for multiple files */
  247.     /* -- fflag = fflag ^ (nfile > 0); */
  248.  
  249.     for(i = 1; i < argc; ++i)
  250.     {
  251.       if((p = argv[i]) != 0)
  252.       {
  253.         if(strchr(p, '?') || strchr(p, '*'))
  254.         {
  255.           /* -- JAJ wildcard support start (does support directory wildcard ie c:\tmp\*.* */
  256.           path = split_file_name(p);               /* get the path BKB */
  257.           err = findfirst(p, &findbuf, 0);
  258.           while(!err)
  259.           {
  260.             strcat(path, findbuf.ff_name);         /* Add the new filename to path BKB */
  261.             if((f = fopen(path, "r")) == NULL)
  262.             {
  263.               cant(findbuf.ff_name);
  264.               break;
  265.             }
  266.             else
  267.             {
  268.               count++;
  269.               grep(f, path);      /* Let grep display the filename including path BKB */
  270.               fclose(f);
  271.             }
  272.             err = findnext(&findbuf);
  273.             path = split_file_name(p);
  274.           }
  275.           /*-- JAJ wildcard support end */
  276.         }
  277.         else
  278.         {
  279.           if((f = fopen(p, "r")) == NULL)
  280.             cant(p);
  281.           else
  282.           {
  283.             count++;
  284.             grep(f, p);
  285.             fclose(f);
  286.           }
  287.         }
  288.       }
  289.     }
  290.     if(count == 0)
  291.       usage("No files found");
  292.   }
  293.   return 0;
  294. }
  295.  
  296. static char *split_file_name(char *file_name)
  297. {
  298.   static char path[256];  /* Path must be static since it is used by main BKB */
  299.   int a;
  300.  
  301.   for(a = 0; file_name[a] != '\0'; a++)
  302.     path[a] = toupper(file_name[a]);
  303.   path[a] = '\0';
  304.  
  305.   /* Remove all char's from the end until the first '\\' or ':' is met, BKB */
  306.   for(a = strlen(path); path[a] != '\\' && path[a] != ':' && a >= 0; a--)
  307.     path[a] = '\0';
  308.   return path;
  309. }
  310.  
  311. /*******************************************************/
  312. static void file(char *s)
  313. {
  314.   /*-- JAJ better file handling for Zeus */
  315.   /*   printf("File %s:\n", s);          */
  316.   printf("%-13s |", s);
  317. }
  318.  
  319. /*******************************************************/
  320. static void cant(char *s)
  321. {
  322.   /*-- JAJ pipe to stdout so Zeus can see it    */
  323.   /*   fprintf(stderr, "%s: cannot open\n", s); */
  324.   fprintf(stdout, "%s: cannot open\n", s);
  325. }
  326.  
  327. /*******************************************************/
  328. static void help(char **hp) /* Give good help */
  329. {
  330.   register char   **dp;
  331.   for(dp = hp; *dp; dp++)
  332.     printf("%s\n", *dp);
  333. }
  334.  
  335. /*******************************************************/
  336. static void usage(char *s)
  337. {
  338.   /*-- JAJ pipe to stdout so Zeus can see it
  339.   //   fprintf(stderr, "?GREP-E-%s\n", s);
  340.   //   fprintf(stderr,
  341.   //   "Usage: grep [-cfnv] pattern [file ...].  grep ? for help\n"); */
  342.   fprintf(stdout, "?GREP-E-%s\n", s);
  343.   fprintf(stdout, "Usage: grep [-cfnvi] pattern [file ...].  grep ? for help\n");
  344.   exit(1);
  345. }
  346. /*******************************************************/
  347. static void compile(char *source) /* Pattern to compile     */
  348. { /* Compile the pattern into global pbuf[] */
  349.   register char  *s;           /* Source string pointer     */
  350.   register char  *lp = NULL;   /* Last pattern pointer    */
  351.   register int   c;            /* Current character     */
  352.   int       o;                 /* Temp        */
  353.   char      *spp;              /* Save beginning of pattern */
  354.  
  355.   s = source;
  356.   if(debug)
  357.     printf("Pattern = \"%s\"\n", s);
  358.   pp = pbuf;
  359.   while((c = *s++) != 0)
  360.   { /* STAR, PLUS and MINUS are special. */
  361.     if(c == '*' || c == '+' || c == '-')
  362.     {
  363.       if(pp == pbuf || (o = pp[-1]) == BOL || o == EOL ||
  364.          o == STAR ||  o == PLUS || o == MINUS)
  365.         badpat("Illegal occurrance op.", source, s);
  366.       store(ENDPAT);
  367.       store(ENDPAT);
  368.       spp = pp;     /* Save pattern end   */
  369.       while(--pp > lp)   /* Move pattern down  */
  370.       *pp = pp[-1];  /* one byte     */
  371.       *pp = (c == '*') ? STAR : (c == '-') ? MINUS : PLUS;
  372.       pp = spp;     /* Restore pattern end  */
  373.       continue;
  374.     }
  375.     /* All the rest. */
  376.     lp = pp;          /* Remember start       */
  377.     switch(c)
  378.     {
  379.       case '^':
  380.         store(BOL);
  381.         break;
  382.       case '$':
  383.         store(EOL);
  384.         break;
  385.       case '.':
  386.         store(ANY);
  387.         break;
  388.       case '[':
  389.         s = cclass(source, s);
  390.         break;
  391.       case ':':
  392.         if(*s)
  393.         {
  394.           c = *s++;
  395.           switch(grep_tolower(c))
  396.           {
  397.             case 'a':
  398.             case 'A':
  399.               store(ALPHA);
  400.               break;
  401.             case 'd':
  402.             case 'D':
  403.               store(DIGIT);
  404.               break;
  405.             case 'n':
  406.             case 'N':
  407.               store(NALPHA);
  408.               break;
  409.             case ' ':
  410.               store(PUNCT);
  411.               break;
  412.             default:
  413.               badpat("Unknown : type", source, s);
  414.               break;
  415.           }
  416.           break;
  417.         }
  418.         else
  419.           badpat("No : type", source, s);
  420.       case '\\':
  421.         if(*s)
  422.           c = *s++;
  423.       default:
  424.         store(CHAR);
  425.         store(grep_tolower(c));
  426.         break;
  427.     }
  428.   }
  429.   store(ENDPAT);
  430.   store(0);         /* Terminate string     */
  431.  
  432.   if(debug)
  433.   {
  434.     for(lp = pbuf; lp < pp;)
  435.     {
  436.       if((c = (*lp++ & 0377)) < ' ')
  437.         printf("\\%o ", c);
  438.       else
  439.         printf("%c ", c);
  440.     }
  441.     printf("\n");
  442.   }
  443. }
  444.  
  445. /*******************************************************/
  446. static char *cclass(char *source, char *src)
  447. /* Compile a class (within []) */
  448. {
  449.   register char   *s;       /* Source pointer    */
  450.   register char   *cp;      /* Pattern start      */
  451.   register int    c;        /* Current character */
  452.   int        o;             /* Temp      */
  453.   s = src;
  454.   o = CLASS;
  455.  
  456.   if(*s == '^')
  457.   {
  458.     ++s;
  459.     o = NCLASS;
  460.   }
  461.   store(o);
  462.   cp = pp;
  463.   store(0);             /* Byte count  */
  464.  
  465.   while((c = *s++) != 0 && c != ']')
  466.   {
  467.     if(c == '\\')  /* Store quoted char    */
  468.     {
  469.       if((c = *s++) == '\0')      /* Gotta get something  */
  470.         badpat("Class terminates badly", source, s);
  471.       else
  472.         store(grep_tolower(c));
  473.     }
  474.     else if(c == '-' && (pp - cp) > 1 && *s != ']' && *s != '\0')
  475.     {
  476.       c = pp[-1];       /* Range start     */
  477.       pp[-1] = RANGE;   /* Range signal    */
  478.       store(c);     /* Re-store start  */
  479.       c = *s++;     /* Get end char and*/
  480.       store(grep_tolower(c));  /* Store it      */
  481.     }
  482.     else
  483.     {
  484.       store(grep_tolower(c));  /* Store normal char */
  485.     }
  486.   }
  487.   if(c != ']')
  488.     badpat("Unterminated class", source, s);
  489.   if((c = (pp - cp)) >= 256)
  490.     badpat("Class too large", source, s);
  491.   if(c == 0)
  492.     badpat("Empty class", source, s);
  493.   *cp = c;
  494.  
  495.   return s;
  496. }
  497. /*******************************************************/
  498. static void store(char op)
  499. {
  500.   if(pp >= &pbuf[PMAX])
  501.     error("Pattern too complex\n");
  502.   *pp++ = op;
  503. }
  504. /*******************************************************/
  505.  
  506. static void badpat(char *message, char *source, char *stop)
  507. {
  508.   /*-- JAJ pipe to stdout so Zeus can see it
  509.   //   fprintf(stderr, "-GREP-E-%s, pattern is\"%s\"\n", message, source);
  510.   //   fprintf(stderr, "-GREP-E-Stopped at byte %d, '%c'\n",
  511.   //    stop-source, stop[-1]); */
  512.   fprintf(stdout, "-GREP-E-%s, pattern is\"%s\"\n", message, source);
  513.   fprintf(stdout, "-GREP-E-Stopped at byte %d, '%c'\n", stop-source, stop[-1]);
  514.   error("?GREP-E-Bad pattern\n");
  515. }
  516.  
  517. static void grep(FILE *fp, char *fn)
  518. { /* Scan the file for the pattern in pbuf[] */
  519.   register int lno, count, m;
  520.   lno = 0;
  521.   count = 0;
  522.   while (fgets(lbuf, LMAX, fp))
  523.   {
  524.     ++lno;
  525.     m = match();
  526.     if((m && !vflag) || (!m && vflag))
  527.     {
  528.       ++count;
  529.       if(!cflag)
  530.       {
  531.         if(fflag && fn)
  532.         {
  533.           file(fn);
  534.           /*-- JAJ better file handling for Zeus */
  535.           /*  fn = 0;  */
  536.         }
  537.         if(nflag)
  538.           printf(" %5.5d | ", lno);
  539.  
  540.         if(lbuf[strlen(lbuf) - 1] == '\n')  /* Remove '\n' from line before it is printed */
  541.           lbuf[strlen(lbuf) - 1] = '\0';
  542.         printf("%s\n", lbuf);
  543.       }
  544.     }
  545.   }
  546.   if(cflag)
  547.   {
  548.     if(fflag && fn)
  549.       file(fn);
  550.     printf(" Count: %d\n", count);
  551.   }
  552. }
  553. /*******************************************************/
  554. static int match(void)
  555. /* Match the current line (in lbuf[]), return 1 if it does. */
  556. {
  557.   register char   *l;         /* Line pointer     */
  558.  
  559.   for(l = lbuf; *l; l++)
  560.   {
  561.     if(pmatch(l, pbuf))
  562.     return(1);
  563.   }
  564.   return(0);
  565. }
  566. /*******************************************************/
  567. static char *pmatch(char *line, char *pattern)
  568. {
  569.   register char   *l;         /* Current line pointer       */
  570.   register char   *p;         /* Current pattern pointer      */
  571.   register char   c;        /* Current character        */
  572.   char       *e;        /* End for STAR and PLUS match  */
  573.   int        op;        /* Pattern operation        */
  574.   int        n;       /* Class counter          */
  575.   char       *are;      /* Start of STAR match        */
  576.  
  577.   l = line;
  578.   if(debug > 1)
  579.     printf("pmatch(\"%s\")\n", line);
  580.   p = pattern;
  581.   while ((op = *p++) != ENDPAT)
  582.   {
  583.     if(debug > 1)
  584.       printf("byte[%d] = 0%o, '%c', op = 0%o\n", l-line, *l, *l, op);
  585.     switch(op)
  586.     {
  587.       case CHAR:
  588.         if(grep_tolower(*l) != *p++)
  589.           return(0);
  590.         l++;
  591.         break;
  592.       case BOL:
  593.         if(l != lbuf)
  594.           return(0);
  595.         break;
  596.       case EOL:
  597.         if(*l != '\0')
  598.           return(0);
  599.         break;
  600.       case ANY:
  601.         if(*l++ == '\0')
  602.           return(0);
  603.         break;
  604.       case DIGIT:
  605.         if((c = *l++) < '0' || (c > '9'))
  606.           return(0);
  607.         break;
  608.       case ALPHA:
  609.         c = grep_tolower(*l);
  610.         l++;
  611.         if(c < 'a' || c > 'z')
  612.           return(0);
  613.         break;
  614.       case NALPHA:
  615.         c = grep_tolower(*l);
  616.         l++;
  617.         if(c >= 'a' && c <= 'z')
  618.           break;
  619.         else if(c < '0' || c > '9')
  620.           return(0);
  621.         break;
  622.       case PUNCT:
  623.         c = *l++;
  624.         if(c == 0 || c > ' ')
  625.           return(0);
  626.         break;
  627.       case CLASS:
  628.       case NCLASS:
  629.         c = grep_tolower(*l);
  630.         l++;
  631.         n = *p++ & 0377;
  632.         do
  633.         {
  634.           if(*p == RANGE)
  635.           {
  636.             p += 3;
  637.             n -= 2;
  638.             if(c >= p[-2] && c <= p[-1])
  639.               break;
  640.           }
  641.           else if(c == *p++)
  642.             break;
  643.         }
  644.         while (--n > 1);
  645.         if((op == CLASS) == (n <= 1))
  646.           return(0);
  647.         if(op == CLASS)
  648.           p += n - 2;
  649.         break;
  650.       case MINUS:
  651.         e = pmatch(l, p);   /* Look for a match  */
  652.         while (*p++ != ENDPAT); /* Skip over pattern */
  653.         if(e)      /* Got a match?  */
  654.           l = e;     /* Yes, update string  */
  655.         break;      /* Always succeeds */
  656.       case PLUS:      /* One or more ... */
  657.         if((l = pmatch(l, p)) == 0)
  658.           return(0);     /* Gotta have a match  */
  659.       case STAR:      /* Zero or more ...  */
  660.         are = l;    /* Remember line start */
  661.         while(*l != 0 && (e = pmatch(l, p)) != 0)
  662.           l = e;     /* Get longest match */
  663.         while (*p++ != ENDPAT); /* Skip over pattern */
  664.         while (l >= are)
  665.         {  /* Try to match rest */
  666.           if((e = pmatch(l, p)) != 0)
  667.             return(e);
  668.           --l;     /* Nope, try earlier */
  669.         }
  670.         return(0);    /* Nothing else worked */
  671.       default:
  672.         printf("Bad op code %d\n", op);
  673.         error("Cannot happen -- match\n");
  674.     }
  675.   }
  676.   return(l);
  677. }
  678. /*******************************************************/
  679. static void error(char *s)
  680. {
  681.   /*-- JAJ pipe to stdout so Zeus can see it
  682.   //   fprintf(stderr, "%s", s); */
  683.   fprintf(stdout, "%s", s);
  684.   exit(1);
  685. }
  686.  
  687. static char grep_tolower(char c)
  688. { /* Ignore case only if specified by user BKB */
  689.   if(ignore_case_flag)
  690.     return tolower(c);
  691.   return c;
  692. }
  693.  
  694.  
  695.